////////////////////////////////////////////////////////////////////////////////
//                                                                            //
//  Filename:    IncanGoddessGuts.cpp                                         //
//  Developer:   Tommy Riddle                                                 //
//  Create date: 10/09/2006                                                   //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////
//                                                                            //
//  Description: This is the class implementation for the slot gut of the     //
//               Australian Slot machine called Incan Goddess.                //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////
//        Copyright (c) 2006 - Realtime Gaming.  All rights reserved.         //
////////////////////////////////////////////////////////////////////////////////

#include "IncanGoddessGuts.h"

////////////////////////////////////////////////////////////////////////////
/// IncanGoddessGuts::m_aLineWinSpinResults
///
/// This array is used to quickly get the SpinResult for the number of
/// icons.  It is indexed by wheel icon and then count.  So to get the
/// SpinResult for 5 Aces, use the following code snippet:
///
///		SpinResult result = m_aLineWinSpinResults[ ICON_ACE ][ 5 ];
///
/// In this example result would be set to EVT_FIVE_ACE.
////////////////////////////////////////////////////////////////////////////
IncanGoddessGuts::SpinResult IncanGoddessGuts::m_aLineWinSpinResults[][6] = 
{
// Number of icons:			0			1				2					3					4					5
/* ICON_NINE */			{	EVT_LOSER,	EVT_LOSER,		EVT_LOSER,			EVT_THREE_NINE,		EVT_FOUR_NINE,		EVT_FIVE_NINE		},
/* ICON_TEN */			{	EVT_LOSER,	EVT_LOSER,		EVT_LOSER,			EVT_THREE_TEN,		EVT_FOUR_TEN,		EVT_FIVE_TEN		},
/* ICON_JACK */			{	EVT_LOSER,	EVT_LOSER,		EVT_LOSER,			EVT_THREE_JACK,		EVT_FOUR_JACK,		EVT_FIVE_JACK		},
/* ICON_QUEEN */		{	EVT_LOSER,	EVT_LOSER,		EVT_LOSER,			EVT_THREE_QUEEN,	EVT_FOUR_QUEEN,		EVT_FIVE_QUEEN		},
/* ICON_KING */			{	EVT_LOSER,	EVT_LOSER,		EVT_LOSER,			EVT_THREE_KING,		EVT_FOUR_KING,		EVT_FIVE_KING		},
/* ICON_ACE */			{	EVT_LOSER,	EVT_LOSER,		EVT_LOSER,			EVT_THREE_ACE,		EVT_FOUR_ACE,		EVT_FIVE_ACE		},
/* ICON_CARVING */		{	EVT_LOSER,	EVT_LOSER,		EVT_TWO_CARVING,	EVT_THREE_CARVING,	EVT_FOUR_CARVING,	EVT_FIVE_CARVING	},
/* ICON_JEWELRY */		{	EVT_LOSER,	EVT_LOSER,		EVT_TWO_JEWELRY,	EVT_THREE_JEWELRY,	EVT_FOUR_JEWELRY,	EVT_FIVE_JEWELRY	},
/* ICON_VASE */			{	EVT_LOSER,	EVT_LOSER,		EVT_TWO_VASE,		EVT_THREE_VASE,		EVT_FOUR_VASE,		EVT_FIVE_VASE		},
/* ICON_SUN */			{	EVT_LOSER,	EVT_LOSER,		EVT_TWO_SUN,		EVT_THREE_SUN,		EVT_FOUR_SUN,		EVT_FIVE_SUN		},
/* ICON_TEMPLE */		{	EVT_LOSER,	EVT_LOSER,		EVT_LOSER,			EVT_LOSER,			EVT_LOSER,			EVT_LOSER			},
/* ICON_GODDESS */		{	EVT_LOSER,	EVT_LOSER,		EVT_TWO_GODDESS,	EVT_THREE_GODDESS,	EVT_FOUR_GODDESS,	EVT_FIVE_GODDESS	}
};

////////////////////////////////////////////////////////////////////////////
/// IncanGoddessGuts::m_aScatterWinSpinResults
///
/// This array is used to quickly get the SpinResult for the number of
/// icons.  It is indexed by icon count only, since Temples are the only
/// scatter win.  So to get the SpinResult for 5 Temples, use the
/// following code snippet:
///
///		SpinResult result = m_aScatterWinSpinResults[ 5 ];
///
/// In this example result would be set to EVT_FIVE_TEMPLE.
////////////////////////////////////////////////////////////////////////////
IncanGoddessGuts::SpinResult IncanGoddessGuts::m_aScatterWinSpinResults[6] = 
{
	// ICON_TEMPLE		   Number of Icons
	EVT_LOSER,			// 0
	EVT_LOSER,			// 1
	EVT_TWO_TEMPLE,		// 2
	EVT_THREE_TEMPLE,	// 3
	EVT_FOUR_TEMPLE,	// 4
	EVT_FIVE_TEMPLE		// 5
};

////////////////////////////////////////////////////////////////////////////
/// IncanGoddessGuts::m_aPayLineOffsets
///
/// Declare an array to look up the offset for a reel for each payline.
/// An offset of 0 means that the reel should stop on the middle row,
/// an offset of 1 means the reel should stop on the top row,
/// and an offset of -1 means the reel should stop on the bottom row.
/// So the offset is relative to the middle row, or pay line 1.
////////////////////////////////////////////////////////////////////////////
int8 IncanGoddessGuts::m_aPayLineOffsets[20][5] = 
{
	{  0,  0,  0,  0,  0 },	// Pay line 1
	{  1,  1,  1,  1,  1 },	// Pay line 2
	{ -1, -1, -1, -1, -1 },	// Pay line 3
	{  1,  0, -1,  0,  1 },	// Pay line 4
	{ -1,  0,  1,  0, -1 },	// Pay line 5
	{  0,  1,  0,  1,  0 },	// Pay line 6
	{  0, -1,  0, -1,  0 },	// Pay line 7
	{  1,  0,  1,  0,  1 },	// Pay line 8
	{ -1,  0, -1,  0, -1 },	// Pay line 9
	{  0,  1,  1,  1,  0 },	// Pay line 10
	{  0, -1, -1, -1,  0 },	// Pay line 11
	{ -1, -1,  0, -1, -1 },	// Pay line 12
	{  1,  1,  0,  1,  1 },	// Pay line 13
	{ -1,  0,  0,  0, -1 },	// Pay line 14
	{  1,  0,  0,  0,  1 },	// Pay line 15
	{  1, -1,  1, -1,  1 },	// Pay line 16
	{ -1,  1, -1,  1, -1 },	// Pay line 17
	{  0,  0,  1,  0,  0 },	// Pay line 18
	{  0,  0, -1,  0,  0 },	// Pay line 19
	{ -1, -1,  1, -1, -1 }	// Pay line 20
};

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::IncanGoddessGuts()   
///
/// This is the default constructor for the class.
////////////////////////////////////////////////////////////////////////////
IncanGoddessGuts::IncanGoddessGuts()
{
	// If this doesn't compile it means that someone probably added
	// another icon type but did not update the
	// m_aLineWinSpinResults array. 
	C_ASSERT( countof( m_aLineWinSpinResults ) == NUM_VALID_ICONS );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::~IncanGoddessGuts()
///
/// Destructor for IncanGoddessGuts. 
////////////////////////////////////////////////////////////////////////////
IncanGoddessGuts::~IncanGoddessGuts()
{
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::SpinResult IncanGoddessGuts::getGoddessLineWinResult() const
///
/// @param   stops			The array of stop positions for the line.
/// @param	 nNumIcons		An output parameter with the number of icons found.
///
/// @return	 SpinResult		A payout from the SpinResult enumeration.
///
/// This helper method is used to calculate the Goddess line win payout.
////////////////////////////////////////////////////////////////////////////
IncanGoddessGuts::SpinResult IncanGoddessGuts::getGoddessLineWinResult( uint8* stops, int32& nNumIcons ) const
{
	// Count how many Goddess icons we have at the beginning of the line.
	for ( uint8 i = 0; i < getReelCount(); i++ )
    {
		// If this icon is not a Goddess...
		if ( getIcon( getGameModeId(), i, stops[i] )->iconid != ICON_GODDESS )
		{
			// then we have our total.
			break;
		}
    }

	// Set the out parameter.
	nNumIcons = i;

	// Use the array to look up the spin result for i Goddess icons.
	return lookupLineWinSpinResult( ICON_GODDESS, i );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::SpinResult IncanGoddessGuts::getLineWinResultWithPossibleSubstitution( uint8* stops, uint8 testIcon, bool& bSubstitution ) const
///
/// @param   stops			The array of stop positions for the line.
/// @param   testIcon		The icon we are looking for, typically the first icon on the line.
/// @param	 nNumIcons		An output parameter with the number of icons found.
/// @param   bSubstitution	An output parameter that is set if a subsitution is used.
///
/// @return	 SpinResult		A payout from the SpinResult enumeration.
///
/// This helper method is used to calculate the line win payout with a possible substitution.
////////////////////////////////////////////////////////////////////////////
IncanGoddessGuts::SpinResult IncanGoddessGuts::getLineWinResultWithPossibleSubstitution(
	uint8* stops, uint8 line, uint8 testIcon, int32& nNumIcons, bool& bSubstitution )
{
	// Initialize the out parameter
	bSubstitution = false;

	// Loop through the remaining reels until the streak is over
	for ( int i = 1; i < getReelCount(); i++ )
    {
		// See what this icon is.
		uint8 currentIcon = getIcon( getGameModeId(), i, stops[i] )->iconid;

		// If the current icon is a Goddess...
		if ( currentIcon == ICON_GODDESS )
		{
			// then use it as a substitution symbol and continue on.
			bSubstitution = true;
			continue;
		}
		// Otherwise if the test icon is a Goddess...
		else if ( testIcon == ICON_GODDESS )
		{
			// then we still have a substitution.  In this case the line
			// started out with a Goddess.
			bSubstitution = true;

			// We have now found our first icon that is not a Goddess.  All
			// of the rest of the icons will have to match this one now for the
			// streak to continue, so change our test icon.
			testIcon = currentIcon;
			continue;
		}

		// If the current icon is not the test icon...
        if ( currentIcon != testIcon )
        {
			// then the streak is over so break out of the loop
			break;
        }
    }

	// If the testIcon is not the scatter symbol...
	if ( testIcon != ICON_TEMPLE )
	{
		// then set the out parameter.
		nNumIcons = i;
	}
	// Otherwise...
	else
	{
		// set the out parameter to 0.
		nNumIcons = 0;
	}

	// If we made it all the way through and the testIcon is still a Goddess...
	if ( testIcon == ICON_GODDESS )
	{
		// then this must be a 5 Goddess win, so reset the substitution flag.
		bSubstitution = false;
	}

	// Use the array to look up the spin result for i testIcons.
	return lookupLineWinSpinResult( testIcon, i );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 bool IncanGoddessGuts::doesGoddessLineWinPayMore( SpinResult srGoddess, SpinResult srOther, bool bSubstitution ) const
///
/// @param   srGoddess      The spin result to calculate the Goddess payout for.
/// @param   srOther		The spin result to calculate the other payout for.
/// @param   bSubstitution	Set if we should use the substitution multiplier with the other payout.
///
/// @return	 bool			True if the Goddess win is larger.
///
/// This helper method is used to see which payout is larger.
////////////////////////////////////////////////////////////////////////////
bool IncanGoddessGuts::doesGoddessLineWinPayMore( SpinResult srGoddess,
	SpinResult srOther, bool bSubstitution ) const
{
	// Lookup the payout for the Goddess line win.
	long nGoddessLineWinPayout = getPayout( srGoddess )->payoutAmount;

	// If we are in free spin mode...
	if ( getGameModeId() == MODE_FREE_SPIN )
	{
		// then factor in the multiplier.
		nGoddessLineWinPayout *= MULTIPLIER_FREE_SPIN_SUBSTITUTION;
	}

	// See what the total payout for the other line win is without the substitution
	// multiplier (if any).
	long nOtherLineWinPayout = getPayout( srOther )->payoutAmount;

	// If we had a substitution and we are in free spin mode...
	if ( bSubstitution && getGameModeId() == MODE_FREE_SPIN )
	{
		// then factor in the multiplier.
		nOtherLineWinPayout *= MULTIPLIER_FREE_SPIN_SUBSTITUTION;
	}

	// Return true if the Goddess payout is bigger.
    return ( nGoddessLineWinPayout > nOtherLineWinPayout );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::classifyStops()   
///
/// @param   stops      The array of stop positions for the line.
/// @param   bet        The value of the line bet.
/// @param   line       The index of the line.
///
/// @return	 uint8      A payout from the SpinResult enumeration as a uint8.    
///
/// This helper method is used to calculate the payout on a line.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::classifyStops( SpinLine* spinline, uint8* stops, uint8 bet, uint8 line,
	uint8& rtnresult, uint8& multiplier )
{
	// Declare some locals to track the number of icons in case it is a 5 of a kind win.
	int32 nNumGoddessIcons = 0;
	int32 nNumIconsWithPossibleSubstitution = 0;

	// Track the Goddess result separately since it might be changed to a substitution.
	SpinResult srGoddessLineWinResult = getGoddessLineWinResult( stops, nNumGoddessIcons );

	// Now see what the payout is if we use any Goddess we find as substitution symbols.
	// Start with the icon on the first reel.
	uint8 testIcon = getIcon( getGameModeId(), 0, stops[0] )->iconid;

	// Declare a local variable to track whether or not we found a substitution.
	bool bSubstitution = false;

	// See what the spin result is if we used any Goddess icons as substitution symbols.
	SpinResult srResultWithPossibleSubstitution =
		getLineWinResultWithPossibleSubstitution( stops, line, testIcon,
		nNumIconsWithPossibleSubstitution, bSubstitution );

	// Use the larger payout between the regular Goddess evaluation compared to
	// the evaluation with the possible substitution.
    if ( doesGoddessLineWinPayMore( srGoddessLineWinResult,
		srResultWithPossibleSubstitution, bSubstitution ) )
	{
		// The Goddess icons by themselves pay out more so use the Goddess result.
		rtnresult = srGoddessLineWinResult;

		// If we did actually have a winning spin...
		if ( srGoddessLineWinResult != EVT_LOSER )
		{
			// then if we are in free spin mode...
			if ( getGameModeId() == MODE_FREE_SPIN )
			{
				// then add a note and change the multiplier.
				spinline->addNote( NOTE_FREE_SPIN_SUBSTITUTION );
				multiplier = MULTIPLIER_FREE_SPIN_SUBSTITUTION;
			}
		}

		// See if it is a 5 of a kind win...
		if ( nNumGoddessIcons == 5 )
		{
			// and set the feature triggered flag if it is.
			setPaylineFeatureTrigger( line, dosftFeatureTriggered );
		}
		// Otherwise see if it is a near miss...
		else if ( nNumGoddessIcons == 4 )
		{
			// and set the near miss flag if it is.
			setPaylineFeatureTrigger( line, dosftNearMiss );
		}
	}
	else
	{
		// Otherwise the result with the substitution (if any) paid more so use it.
		rtnresult = srResultWithPossibleSubstitution;

		// If we did actually have a substitution and a winning spin...
		if ( bSubstitution && srResultWithPossibleSubstitution != EVT_LOSER )
		{
			// then if we are in free spin mode...
			if ( getGameModeId() == MODE_FREE_SPIN )
			{
				// then add a note and change the multiplier.
				spinline->addNote( NOTE_FREE_SPIN_SUBSTITUTION );
				multiplier = MULTIPLIER_FREE_SPIN_SUBSTITUTION;
			}
			// Otherwise...
			else
			{
				// then just add a note.
				spinline->addNote( NOTE_SUBSTITUTION );
			}
		}

		// See if it is a 5 of a kind win...
		if ( nNumIconsWithPossibleSubstitution == 5 )
		{
			// and set the feature triggered flag if it is.
			setPaylineFeatureTrigger( line, dosftFeatureTriggered );
		}
		// Otherwise see if it is a near miss...
		else if ( nNumIconsWithPossibleSubstitution == 4 )
		{
			// and set the near miss flag if it is.
			setPaylineFeatureTrigger( line, dosftNearMiss );
		}
	}
}

////////////////////////////////////////////////////////////////////////////
/// @fn		IncanGoddessGuts::tryForJackpot()
///
///	This function tries to win the jackpot and adds the scatter if we hit
/// the jackpot.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::tryForJackpot( SlotSpin* spin )
{
	// If we are in the first paid spin (NOT a bonus spin)...
	if ( getGameModeId() == MODE_NORMAL ) 
	{
		// then try for the Random Progressive Jackpot
		// If we hit the jackpot...
		if ( getRandomProgressiveJackpotResult( getNumLinesBet() ) || 
             m_bForceRandomJackpot == true )
		{
			// then add a jackpot scatter to the spin.
			spin->addScatter( 0,
				getPayout( EVT_RANDOM_PROGRESSIVE_WINNER )->payoutAmount, 1,
				true, EVT_RANDOM_PROGRESSIVE_WINNER, 0 );
		}
    }
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::getScatterWinResult()   
///
/// @param   spin						The current spin object.
/// @param   nNumScatterIcons			The number of scatter icons that we find.
/// @param   nScatterPositions			The bit mask for the scatters positions that we find.
///
/// @return	 SpinResult			A payout from the SpinResult enumeration.
///
/// This helper method is used to evaluate the spin for scatters.
////////////////////////////////////////////////////////////////////////////
IncanGoddessGuts::SpinResult IncanGoddessGuts::getScatterWinResult( SlotSpin* spin, int32& nNumScatterIcons, int32& nScatterPositions ) const
{
	// Initialize the scatterPositions.
	nScatterPositions = 0;

	// Loop through each reel.
  	for( int reel = 0; reel < getReelCount(); reel++ )
	{
		// The position bit for the current icon in case it needs to be stored in a mask.
		uint8 position = reel;

		// Loop through each row in the wheel house.
		for( int row = 0; row < 3; row++ )
		{
			// Get the current stop.
			uint8 stop = relativeWheelPosition( getGameModeId(), reel,
				spin->getStops()[ reel ], row );

			// Get the icon for the current stop.
			uint8 currentIcon = getIcon( getGameModeId(), reel, stop )->iconid;
			
			// If the icon is the one we are looking for...
			if ( currentIcon == ICON_TEMPLE )
			{
				// then increase the icon count
				nNumScatterIcons++;

				// Set the bit in the position mask for this stop.
				nScatterPositions |= 1 << position;
			}

			// Move to the next bit in the position mask.
            position += 5;
		}
	}

	// Use the array to look up the spin result for nNumScatterIcons.
	return lookupScatterWinSpinResult( nNumScatterIcons );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::evaluateScatterWin()
///
/// This function determines if the current spin wins a scatter payment and
/// adds the scatter.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::evaluateScatterWin( SlotSpin* spin, SpinResult srScatterResult, int32 nScatterPositions )
{
	// If we got the scatter win...
	if ( srScatterResult != EVT_LOSER )
	{
		// then initialize our multiplier for scatters.
		int32 multiplier = 1;

		// If we are in free spin mode...
		if ( getGameModeId() == MODE_FREE_SPIN )
		{
			// then use the free spin scatter multiplier.
			multiplier = MULTIPLIER_FREE_SPIN;
		}

		// Get the base payout for the scatter.
		int32 nPayout = getPayout( srScatterResult )->payoutAmount;
		
		// Scatters payout based on the total bet, so factor in the number of bet lines.
		nPayout *= getNumLinesBet();

		// Add the payout (with multiplier) to the total win for this spin.
		increaseTotalWin( nPayout * multiplier );
    
		// Add a scatter to the spin.
		SlotScatter* pScatter = spin->addScatter( nScatterPositions,
			nPayout * getBetPerLine(), multiplier, false, srScatterResult, 0 );

		// If the free spin multiplier was used...
		if ( multiplier == MULTIPLIER_FREE_SPIN )
		{
			// then add the free spin note.
			pScatter->addNote( NOTE_FREE_SPIN );
		}
	}
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::evaluateFeatureTrigger()
///
/// This function determines if the current spin triggered the free game
/// feature or the bonus game and adds the appropriate scatter.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::evaluateFeatureTrigger( SlotSpin* spin, int32 nNumScatterIcons, int32 nScatterPositions )
{
	// First loop through all of the lines looking for Double or Something features.
	for ( int n = 0; n < spin->getSpinLineCount(); n++ )
	{
		// See what kind of feature trigger the line had, if any.
		DoubleOrSomethingFeatureTrigger dosft = getPaylineFeatureTrigger( n );

		// If the line triggered the feature...
		if ( dosft == dosftFeatureTriggered )
		{
			// then add the double or something scatter.
			spin->addScatter( getPaylineScatterPosition( n ), 0, 0, false, EVT_DOUBLE_OR_SOMETHING_FEATURE, 0 );

			// and increase the number of feature rounds.
			incrementNumDoubleOrSomethingRounds();
		}
		// Otherwise if the line had a near miss...
		else if ( dosft == dosftNearMiss )
		{
			// then add a near miss scatter.
			spin->addScatter( getPaylineScatterPosition( n ), 0, 0, false, EVT_DOUBLE_OR_SOMETHING_NEAR_MISS, 0 );
		}
	}

	// If we have at least 1 scatter icon...
	if ( nNumScatterIcons >= 1 )
	{
		// then we will at the very least have a near miss.
		SpinResult srFeatureTrigger = EVT_FEATURE_NEAR_MISS;

		// We need a place to keep up with how many free spins to award.
		int32 nFreeSpins = 0;

		// If there were at least 3 scatter icons...
		if ( nNumScatterIcons >= 3 )
		{
			// then the spin result is not a near miss any more.
			srFeatureTrigger = EVT_FREE_GAME_FEATURE;

			// Award 12 free spins since it actually triggered.
			nFreeSpins = 12;

			// Add the free spins.
			incrementFreeSpins( nFreeSpins );

			// If we had 5 scatter icons...
			if ( nNumScatterIcons == 5 )
			{
				// then add the double or something feature for the scatter.
				spin->addScatter( nScatterPositions, 0, 0, false, EVT_DOUBLE_OR_SOMETHING_FEATURE, 0 );

				// and increase the number of feature rounds.
				incrementNumDoubleOrSomethingRounds();

				// Set the feature triggered flag.
				setScatterFeatureTrigger( dosftFeatureTriggered );
			}
			// Otherwise if we had 4 scatter icons...
			else if ( nNumScatterIcons == 4 )
			{
				// If the first 4 reels all have scatter icons...
				if ( ( SCATTER_REEL_0_MASK & nScatterPositions ) &&
					( SCATTER_REEL_1_MASK & nScatterPositions ) &&
					( SCATTER_REEL_2_MASK & nScatterPositions ) &&
					( SCATTER_REEL_3_MASK & nScatterPositions ) )
				{
					// then add a near miss scatter.
					spin->addScatter( nScatterPositions, 0, 0, false, EVT_DOUBLE_OR_SOMETHING_NEAR_MISS, 0 );

					// Set the near miss flag.
					setScatterFeatureTrigger( dosftNearMiss );
				}
			}

			// If we don't have any double or something rounds...
			if ( getNumDoubleOrSomethingRounds() == 0 )
			{
				// then go ahead and change the game mode now.
				setGameModeId( MODE_FREE_SPIN );
			}
		}

		// Add a scatter to the spin for the near miss or the feature trigger.
		spin->addScatter( nScatterPositions, 0, 0, false,
			srFeatureTrigger, nFreeSpins );
	}
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::classifyScatters( SlotSpin* spin )
///
/// @param   spin       Pointer to the spin with stop data.
///
/// This is an override of the framework method.  
/// It determines if the current spin merits free spins or scatter payments.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::classifyScatters( SlotSpin* spin )
{	
	// Try for the Random Progressive Jackpot
	tryForJackpot( spin );

	// Declare some local variables to track the scatter positions.
	int32 nScatterPositions = 0;
	int32 nNumScatterIcons = 0;

	// See if there is a scatter win.
	SpinResult srResult = getScatterWinResult( spin, nNumScatterIcons, nScatterPositions );

	// Add the scatter, if any.
	evaluateScatterWin( spin, srResult, nScatterPositions );

	// Add the scatters for the free game feature and the double or something feature.
	evaluateFeatureTrigger( spin, nNumScatterIcons, nScatterPositions );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::isValidGuts()   
///
/// @return	 bool8      True if gut is valid.    
///
/// This helper method is used to see if the current gut ID is valid.
////////////////////////////////////////////////////////////////////////////
bool8 IncanGoddessGuts::isValidGuts()
{
	// Make sure that the m_aLineWinSpinResults array has an entry for each reel,
	// and an additional entry in case there are 0 icons on the line.
	// Note:  we can't do this in the constructor because we have to call
	// getReelCount.
	ASSERT( countof( m_aLineWinSpinResults[0] ) == getReelCount( MODE_NORMAL ) + 1 );

	// Make sure that the m_aScatterWinSpinResults array has an entry for each reel,
	// and an additional entry in case there are 0 icons on the line.
	ASSERT( countof( m_aScatterWinSpinResults ) == getReelCount( MODE_NORMAL ) + 1 );

	// Now make sure that both game modes have the same number of reels.
	ASSERT( getReelCount( MODE_NORMAL ) == getReelCount( MODE_FREE_SPIN ) );

	// Make sure the payline offset array has a column for each reel.
	ASSERT( countof( m_aPayLineOffsets[0] ) == getReelCount( MODE_NORMAL ) );

	// Make sure the payline offset array has a row for each payline.
	ASSERT( countof( m_aPayLineOffsets ) == getPaylineCount() );

	// Finally just check to see if the gut is within the right range.
    return ( getGutsId() >= 2900 && getGutsId() < 3000 );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::getGameModeMultiplierAndNoteId( uint8& nGameModeMultiplier, uint8& nNoteId )
///
/// @param   nGameModeMultiplier	The multiplier for the game mode.
/// @param   nNoteId				The note ID for the game mode.
///
/// This helper method is used to get the game mode multiplier and note ID.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::getGameModeMultiplierAndNoteId( uint8& nGameModeMultiplier,
	uint8& nNoteId )
{
	// If we are in free spin mode...
    if ( getGameModeId() == MODE_FREE_SPIN )
    {
		// then return the free spin multiplier...
        nGameModeMultiplier = MULTIPLIER_FREE_SPIN;

		// and the free spin note.
        nNoteId = NOTE_FREE_SPIN;
    }
	// Otherwise...
    else
    {
		// just defer to the base class.
        SlotGuts::getGameModeMultiplierAndNoteId( nGameModeMultiplier, nNoteId );
    }
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::hasSavedGames()
///
/// @param   bool8   True/False indicating if this machine supports saved gamees.
///
/// This is a framework override to determine if this machine supports saved games.
////////////////////////////////////////////////////////////////////////////
bool8 IncanGoddessGuts::hasSavedGames()
{
	// We support saved games so always return true.
	return true;
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::DoubleOrSomethingFeatureTrigger IncanGoddessGuts::getPaylineFeatureTrigger()
///
/// This helper method is used to get the subgame data that tracks whether
/// a payline triggered the feature.
////////////////////////////////////////////////////////////////////////////
IncanGoddessGuts::DoubleOrSomethingFeatureTrigger IncanGoddessGuts::getPaylineFeatureTrigger( uint8 nPayline )
{
	// Make sure the payline is valid.
	ASSERT( nPayline < getPaylineCount() );

	// Get the subgame data for the payline.
	return (DoubleOrSomethingFeatureTrigger)m_subGame->getData( SGDIDX_PAYLINE_FEATURE_TRIGGER_0 + nPayline );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 void IncanGoddessGuts::setPaylineFeatureTrigger()
///
/// This helper method is used to set the subgame data that tracks whether
/// a payline triggered the feature.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::setPaylineFeatureTrigger( uint8 nPayline, DoubleOrSomethingFeatureTrigger ePaylineResult )
{
	// Make sure the payline is valid.
	ASSERT( nPayline < getPaylineCount() );

	// Update the subgame data for the payline.
	m_subGame->setServerOnly( SGDIDX_PAYLINE_FEATURE_TRIGGER_0 + nPayline, true );
	m_subGame->update( SGDIDX_PAYLINE_FEATURE_TRIGGER_0 + nPayline, ePaylineResult );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::DoubleOrSomethingFeatureTrigger IncanGoddessGuts::getScatterFeatureTrigger()
///
/// This helper method is used to get the subgame data that tracks whether
/// a scatter triggered the feature.
////////////////////////////////////////////////////////////////////////////
IncanGoddessGuts::DoubleOrSomethingFeatureTrigger IncanGoddessGuts::getScatterFeatureTrigger()
{
	// Get the subgame data for the scatter.
	return (DoubleOrSomethingFeatureTrigger)m_subGame->getData( SGDIDX_SCATTER_FEATURE_TRIGGER );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 void IncanGoddessGuts::setScatterFeatureTrigger()
///
/// This helper method is used to set the subgame data that tracks whether
/// a scatter triggered the feature.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::setScatterFeatureTrigger( DoubleOrSomethingFeatureTrigger eScatterResult )
{
	// Update the subgame data for the scatter.
	m_subGame->setServerOnly( SGDIDX_SCATTER_FEATURE_TRIGGER, true );
	m_subGame->update( SGDIDX_SCATTER_FEATURE_TRIGGER, eScatterResult );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 int32 IncanGoddessGuts::getNumDoubleOrSomethingRounds()
///
/// This helper method is used to get the subgame data that tracks how many
/// Double or Something rounds have been awarded.
////////////////////////////////////////////////////////////////////////////
int32 IncanGoddessGuts::getNumDoubleOrSomethingRounds()
{
	// Return the subgame data.
	return m_subGame->getData( SGDIDX_NUM_DOUBLE_OR_SOMETHING_ROUNDS );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 void IncanGoddessGuts::initializeNumDoubleOrSomethingRounds()
///
/// This helper method is used to initialize the subgame data that tracks
/// how many Double or Something rounds have been awarded.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::initializeNumDoubleOrSomethingRounds()
{
	// Initialize the subgame data to 0.
    m_subGame->update( SGDIDX_NUM_DOUBLE_OR_SOMETHING_ROUNDS, 0 );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 void IncanGoddessGuts::incrementNumDoubleOrSomethingRounds()
///
/// This helper method is used to increment the subgame data that tracks
/// how many Double or Something rounds have been awarded.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::incrementNumDoubleOrSomethingRounds()
{
	// Get the current value and then add 1.
    m_subGame->update( SGDIDX_NUM_DOUBLE_OR_SOMETHING_ROUNDS, getNumDoubleOrSomethingRounds() + 1 );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 int32 IncanGoddessGuts::getCurrentDoubleOrSomethingRound()
///
/// This helper method is used to get the subgame data that tracks the
/// current Double or Something round that is being played.
////////////////////////////////////////////////////////////////////////////
int32 IncanGoddessGuts::getCurrentDoubleOrSomethingRound()
{
	// Return the subgame data.
	return m_subGame->getData( SGDIDX_CURRENT_DOUBLE_OR_SOMETHING_ROUND );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::initializeCurrentDoubleOrSomethingRound()
///
/// This helper method is used to initialize the subgame data that tracks
/// the current Double or Something round that is being played.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::initializeCurrentDoubleOrSomethingRound()
{
	// Initialize the subgame data to 0.
    m_subGame->update( SGDIDX_CURRENT_DOUBLE_OR_SOMETHING_ROUND, 0 );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::incrementCurrentDoubleOrSomethingRound()
///
/// This helper method is used to increment the subgame data that tracks
/// the current Double or Something round that is being played.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::incrementCurrentDoubleOrSomethingRound()
{
	// Get the current value and then add 1.
    m_subGame->update( SGDIDX_CURRENT_DOUBLE_OR_SOMETHING_ROUND, getCurrentDoubleOrSomethingRound() + 1 );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::getCurrentDoubleOrSomethingResult()
///
/// This helper method is used to get the subgame data that tracks 
/// whether or not the current Double or Something game is a winner.
////////////////////////////////////////////////////////////////////////////
bool IncanGoddessGuts::getCurrentDoubleOrSomethingResult()
{
	// If the subgame data is true...
	if ( m_subGame->getData( SGDIDX_CURRENT_DOUBLE_OR_SOMETHING_RESULT ) )
	{
		// then return true.
		return true;
	}

	// Otherwise return false.
	return false;
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::setCurrentDoubleOrSomethingResult()
///
/// This helper method is used to set the subgame data that tracks 
/// whether or not the current Double or Something game is a winner.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::setCurrentDoubleOrSomethingResult( bool bResult )
{
	// Just store the new value in the subgame data.
    m_subGame->update( SGDIDX_CURRENT_DOUBLE_OR_SOMETHING_RESULT, bResult );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::getCurrentDoubleOrSomethingBetAmount()
///
/// This helper method is used to get the subgame data that tracks the
/// original line or scatter win amount for the current Double or Something
/// round.
////////////////////////////////////////////////////////////////////////////
int32 IncanGoddessGuts::getCurrentDoubleOrSomethingBetAmount()
{
	// Return the subgame data.
	return m_subGame->getData( SGDIDX_CURRENT_DOUBLE_OR_SOMETHING_BET_AMOUNT );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::findNextDoubleOrSomethingBetAmount()
///
/// This helper method is used to find the original line or scatter win
/// amount for the next Double or Something round.
////////////////////////////////////////////////////////////////////////////
int32 IncanGoddessGuts::findNextDoubleOrSomethingBetAmount()
{
	// Get the next round.
	int32 nNextRound = getCurrentDoubleOrSomethingRound() + 1;

	// Track how many triggers we have found so far.
	int32 nNumTriggers = 0;

	// Loop through all of the paylines.
	for ( uint8 nLine = 0; nLine < getPaylineCount(); nLine++ )
	{
		// If this payline triggered the feature...
		if ( getPaylineFeatureTrigger( nLine ) == dosftFeatureTriggered )
		{
			// then increment the counter.
			nNumTriggers++;

			// If this is the trigger we are looking for...
			if ( nNumTriggers == nNextRound )
			{
				// then return the bet amount from the line win.
				return getSlotSpin()->getSpinLineAt( nLine )->totallinepayoutamount;
			}
		}
	}

	// If we got to here then we must have exactly one more trigger...
	ASSERT( nNumTriggers + 1 == nNextRound );

	// and it has to be the scatter.
	ASSERT( getScatterFeatureTrigger() == dosftFeatureTriggered );

	// Loop through all of the scatters...
	for ( int n = 0; n < getSlotSpin()->getNumScatters(); n++ )
	{
		// and look for the one for the 5 Temple scatter win.
		if ( getSlotSpin()->getScatter( n )->payouttypeid == EVT_FIVE_TEMPLE )
		{
			// Return the win amount for the scatter.
			return getSlotSpin()->getScatter( n )->totalscatterpayoutamount;
		}
	}

	// If we get to here something is wrong.  We had more feature rounds
	// than we had feature triggers.
	ASSERT( false );
	return 0;
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::initializeNextDoubleOrSomethingBetAmount()
///
/// This helper method is used to initialize the subgame data that tracks
/// the original line or scatter win amount for the current Double or
/// Something round.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::initializeNextDoubleOrSomethingBetAmount()
{
	// Initialize the subgame data.
    m_subGame->update( SGDIDX_CURRENT_DOUBLE_OR_SOMETHING_BET_AMOUNT, findNextDoubleOrSomethingBetAmount() );
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::preSpinClassifyResults( SlotSpin* spin )
///
/// @param   spin       Pointer to the spin with stop data.
///
/// This method is overridden when running in debug spins mode to alter the reel stops.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::preSpinClassifyResults( SlotSpin* spin )
{
	// Loop through all of the paylines...
	for ( uint8 n = 0; n < getPaylineCount(); n++ )
	{
		// and initialize the feature triggered flag.
		setPaylineFeatureTrigger( n, dosftNone );
	}

	// Initialized the scatter feature triggered flag.
	setScatterFeatureTrigger( dosftNone );

	// Initialize the Double or Something counters.
	initializeNumDoubleOrSomethingRounds();
	initializeCurrentDoubleOrSomethingRound();

	// Finally call the base class.
	SlotGuts::preSpinClassifyResults(spin);
	return;
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::postSpinClassifyResults( SlotSpin* spin )
///
/// @param   spin       Pointer to the spin with stop data.
///
/// This method is overridden to see if we can complete the game.
////////////////////////////////////////////////////////////////////////////
void IncanGoddessGuts::postSpinClassifyResults( SlotSpin* spin )
{
	// If we reached the maximum payout already...
	if ( getMaxPayoutAwarded() )
	{
		// then clear out the double or something rounds.
		initializeNumDoubleOrSomethingRounds();
	}

	// If we have feature rounds...
	if ( getNumDoubleOrSomethingRounds() )
	{
		// then reset everything for a new game.
		setCurrentDoubleOrSomethingResult( false );
		initializeNextDoubleOrSomethingBetAmount();

		// The game can't complete until the bonus game is done.
		setGameCanComplete( false );
		setBonusGameState( BONUS_GAME_ACTIVE );
	}
}

////////////////////////////////////////////////////////////////////////////
/// @fn 	 IncanGoddessGuts::processClientData()
///
/// @param   bonusGameType      The current type of bonus game.
/// @param   bonusDataCount     The number of integers passed from the client.
/// @param   bonusData			An array of integers passed from the client.
/// 
/// This is an override of the framework method.
/// 
////////////////////////////////////////////////////////////////////////////
bool8 IncanGoddessGuts::processClientData( uint8 bonusGameType, int32 bonusDataCount, int32 *bonusData )
{
	// Make sure the bonus game type is correct (in case somebody is trying to hack us)
	if ( bonusGameType != BONUS_GAME_DOUBLE_OR_SOMETHING )
	{
		return false;
	}

	// Make sure the data count is correct (in case somebody is trying to hack us)
	if ( bonusDataCount != 1 )
	{
		return false;
	}

	// Make sure the pointer is not NULL.  I'm not sure how this could happen, but we
	// don't want to crash if it does.
	if ( bonusData == NULL )
	{
		ASSERT( false );
		return false;
	}

	// Make sure we have bonus games.
	if ( getCurrentDoubleOrSomethingRound() >= getNumDoubleOrSomethingRounds() )
	{
		return false;
	}

	// Store the index that they picked in a local variable.
	int32 nPickIndex = *bonusData;

	// Make sure the pick index is valid (in case somebody is trying to hack us)
	if ( nPickIndex < 0 || nPickIndex >= NUM_DOUBLE_OR_SOMETHING_CHOICES )
	{
		return false;
	}

	// Initialize a local to hold the result.
	int32 nResult = getRandomValueFromProbabilityTable( PROB_TABLE_DOUBLE_OR_SOMETHING_RESULT );

	// If the result matches their pick...
	if ( nPickIndex == nResult )
	{
		// then set the sub-game data to true.
		setCurrentDoubleOrSomethingResult( true );

		// Add the bonus game result.
		addBonusGame( BONUS_GAME_DOUBLE_OR_SOMETHING, getCurrentDoubleOrSomethingRound(),
			getCurrentDoubleOrSomethingBetAmount(), 1, getCurrentDoubleOrSomethingBetAmount(), 0 );
	}
	// Otherwise...
	else
	{
		// then set the sub-game data to false.
		setCurrentDoubleOrSomethingResult( false );
	}

	// Increment the counter.
	incrementCurrentDoubleOrSomethingRound();

	// If we reached the maximum payout already...
	if ( getMaxPayoutAwarded() )
	{
		// then clear out the double or something rounds.
		initializeNumDoubleOrSomethingRounds();
	}
	
	// If we still have another round to go...
	if ( getCurrentDoubleOrSomethingRound() < getNumDoubleOrSomethingRounds() )
	{
		// then reset everything for a new game.
		initializeNextDoubleOrSomethingBetAmount();
	}
	// Otherwise
	else
	{
		// the bonus game is done.
		setGameCanComplete( true );
		setBonusGameState( BONUS_GAME_INACTIVE );

		// If we have free spins...
		if ( getFreeSpins() )
		{
			// then make sure we are in free spin mode.
			// Note:  We might already be in free spin mode, but that is OK.
			setGameModeId( MODE_FREE_SPIN );

			// We should never get into here if we hit the max payout since it should
			// have removed all of the free spins.
			ASSERT( !getMaxPayoutAwarded() );
		}
	}

	return true;
}
